/*
 * graph2D
 * Copyright (c) 2009 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "soundPlayer.h"
#if defined(TARGET_IPHONE)
#include "platform/iOS/soundPlayer_impl.h"
#elif defined(TARGET_WINDOWS)
#include "platform/windows/soundPlayer_impl.h"
#endif
#include <set>

#define GRAPH2D_SOUND_DATAMANAGER_FLAG_LOOP		0x00000001

#define GRAPH2D_SOUND_PLAYER_FLAG_ENABLE		0x00000001
#define GRAPH2D_SOUND_PLAYER_FLAG_LOOPING		0x00000002
#define GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_FADE	0x00000004
#define GRAPH2D_SOUND_PLAYER_FLAG_PITCH_FADE	0x00000008
#define GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_STOP	0x00000010
#define GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_PAUSE	0x00000020

#define VOLUME 0
#define PITCH 1

namespace Graph2D
{
	namespace Sound
	{
		////////////////////////////////////////////////////////////
		static ALCcontext* context;
		static Listener listener;
		static std::set<Player*> players;

		bool initialize()
		{
			// OpneALデバイスを開く
			ALCdevice* device = alcOpenDevice(NULL);
			GRAPH2D_CHECK_AL_ERROR();
			if(alGetError() == AL_NO_ERROR)
			{
				// OpenALコンテキスを作成して、カレントにする
				context = alcCreateContext(device, NULL);
				alcMakeContextCurrent(context);
				GRAPH2D_CHECK_AL_ERROR();
			}

			return alGetError() == AL_NO_ERROR;
		}

		void finalize()
		{
			GRAPH2D_CHECK_AL_ERROR();

			players.clear();

			// OpenALデバイスを取得
			ALCdevice* device = alcGetContextsDevice(context);

			// OpenALコンテキストを解放
			alcMakeContextCurrent(NULL);
			alcDestroyContext(context);

			// OpneALデバイスを閉じる
			alcCloseDevice(device);
		}

		void validate()
		{
			if(alcGetCurrentContext() != context)
			{
				alcMakeContextCurrent(context);
				GRAPH2D_CHECK_AL_ERROR();
			}
		}

		void update(const float second)
		{
			validate();

			for(std::set<Player*>::iterator i = players.begin(); i != players.end(); i++)
			{
				(*i)->update(second);
			}
		}

		static void regist(Player* player)
		{
			players.insert(player);
		}

		static void remove(Player* player)
		{
			std::set<Player*>::iterator i = players.find(player);
			if(i != players.end())
				players.erase(i);
		}

		Listener& getListener()
		{
			return listener;
		}

		////////////////////////////////////////////////////////////
		DataManager::DataManager()
			: buffers(NULL)
			, allocated(NULL)
			, environments(NULL)
			, size(0)
		{
		}

		DataManager::~DataManager()
		{
			finalize();
		}

		void DataManager::initialize(const size_t size)
		{
			finalize();

			buffers = new ALuint[size];

			GRAPH2D_CALL_AL(alGenBuffers(size, buffers));

			const size_t arraySize = (size + 8 - 1) / 8;
			allocated = new unsigned char[arraySize];
			memset(allocated, 0, arraySize);

			environments = new Environment[size];
			memset(environments, 0, sizeof(Environment) * size);

			this->size = size;
		}

		void DataManager::finalize()
		{
			if(buffers)
			{
				GRAPH2D_CALL_AL(alDeleteBuffers(size, buffers));

				delete[] buffers;
				buffers = NULL;

				size = 0;
			}

			delete[] allocated;
			allocated = NULL;

			delete[] environments;
			environments = NULL;
		}

		size_t DataManager::load(const std::string& filename)
		{
			for(size_t index = 0; index < size; index++)
			{
				const size_t mask = 1 << (index % 8);
				if(!(allocated[index / 8] & mask))
				{
#if GRAPH2D_LOG_DETAIL_LEVEL >= 1
					MANA_TRACE("Sound::DataManager::load: %s #%d ... ", filename.c_str(), index);
#endif
					if(!Implementation::Sound::load(buffers[index], filename))
					{
#if GRAPH2D_LOG_DETAIL_LEVEL >= 1
						MANA_TRACE("NG\n");
#endif
						goto ABORT;
					}
#if GRAPH2D_LOG_DETAIL_LEVEL >= 1
					MANA_TRACE("OK\n");
#endif

					allocated[index / 8] |= mask;

					return index;
				}
			}
ABORT:
			return static_cast<size_t>(~0);
		}

		void DataManager::release(const size_t index)
		{
			const size_t mask = 1 << (index & 8);
			if(allocated[index / 8] & mask)
			{
#if GRAPH2D_LOG_DETAIL_LEVEL >= 1
				MANA_TRACE("Sound::DataManager::release: #%d ... OK\n", index);
#endif
	   			GRAPH2D_CALL_AL(alBufferData(buffers[index], AL_FORMAT_MONO8, NULL, 0, 0));

				allocated[index / 8] &= ~mask;
			}
		}

		bool DataManager::getLoop(const size_t index) const
		{
			return (environments[index].flag & GRAPH2D_SOUND_DATAMANAGER_FLAG_LOOP) ? true : false;
		}

		void DataManager::setLoop(const size_t index, const bool enable)
		{
			if(enable)
				environments[index].flag |= GRAPH2D_SOUND_DATAMANAGER_FLAG_LOOP;
			else
				environments[index].flag &= ~GRAPH2D_SOUND_DATAMANAGER_FLAG_LOOP;
		}

		size_t DataManager::getLoopPoint(const size_t index) const
		{
			return environments[index].samplingLoopPoint;
		}

		void DataManager::setLoopPoint(const size_t index, const size_t sampling)
		{
			environments[index].samplingLoopPoint = sampling;
		}

		ALuint DataManager::name(const size_t index)
		{
			return buffers[index];
		}

		////////////////////////////////////////////////////////////
		Listener::Listener()
		{
		}

		void Listener::setPosition(const float x, const float y, const float z)
		{
			const ALfloat vector[3] = { x, y, z };
			GRAPH2D_CALL_AL(alListenerfv(AL_POSITION, vector));
		}

		void Listener::setVelocity(const float x, const float y, const float z)
		{
			const ALfloat vector[3] = { x, y, z };
			GRAPH2D_CALL_AL(alListenerfv(AL_VELOCITY, vector));
		}
		//alListenerfv(AL_ORIENTATION,listenerOri[6]);
		////////////////////////////////////////////////////////////
		Player::Player()
			: dataManager(NULL)
			, environments(NULL)
			, sources(NULL)
		{
		}

		Player::~Player()
		{
			finalize();
		}

		void Player::initialize(const size_t size, DataManager* dataManager)
		{
			finalize();

			sources = new ALuint[size];

			GRAPH2D_CALL_AL(alGenSources(size, sources));

			environments = new Environment[size];
			memset(environments, 0, sizeof(Environment) * size);

			this->size = size;

			this->dataManager = dataManager;

			regist(this);
		}

		void Player::finalize()
		{
			delete[] environments;
			environments = NULL;

			if(sources)
			{
				remove(this);

				GRAPH2D_CALL_AL(alDeleteSources(size, sources));

				delete[] sources;
				sources = NULL;
			}

			dataManager = NULL;

			size = 0;
		}

		void Player::update(const float second)
		{
			for(size_t index = 0; index < size; index++)
			{
				if(environments[index].flag & GRAPH2D_SOUND_PLAYER_FLAG_ENABLE)
				{
					ALint state;
					GRAPH2D_CALL_AL(alGetSourcei(sources[index], AL_SOURCE_STATE, &state));
					if(/*state == AL_INITIAL || */state == AL_STOPPED)
					{
						if(dataManager->getLoop(environments[index].dataManagerIndex))
						{
							if(environments[index].flag & GRAPH2D_SOUND_PLAYER_FLAG_LOOPING)
							{
								environments[index].flag |= GRAPH2D_SOUND_PLAYER_FLAG_LOOPING;
								GRAPH2D_CALL_AL(alSourcei(sources[index], AL_SAMPLE_OFFSET, AL_TRUE));
								GRAPH2D_CALL_AL(alSourcei(sources[index], AL_LOOPING, AL_TRUE));
							}
						}
						else
						{
							environments[index].flag &= ~GRAPH2D_SOUND_PLAYER_FLAG_ENABLE;
						}
					}
					else if(state == AL_PLAYING)
					{
						if(environments[index].flag & GRAPH2D_SOUND_PLAYER_FLAG_PITCH_FADE)
						{
							environments[index].fade[PITCH].current += (environments[index].fade[PITCH].velocity * second);

							environments[index].fade[PITCH].timer -= second;
							if(environments[index].fade[PITCH].timer <= 0)
							{
								environments[index].flag &= ~GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_FADE;
								environments[index].fade[PITCH].current = environments[index].fade[PITCH].target;
							}

							GRAPH2D_CALL_AL(alSourcef(sources[index], AL_PITCH, environments[index].fade[PITCH].current));
						}
						if(environments[index].flag & GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_FADE)
						{
							environments[index].fade[VOLUME].current += (environments[index].fade[VOLUME].velocity * second);

							environments[index].fade[VOLUME].timer -= second;
							if(environments[index].fade[VOLUME].timer <= 0)
							{
								environments[index].flag &= ~GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_FADE;
								environments[index].fade[VOLUME].current = environments[index].fade[VOLUME].target;

								if(environments[index].flag & GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_STOP)
								{
									environments[index].flag &= ~GRAPH2D_SOUND_PLAYER_FLAG_ENABLE;
									GRAPH2D_CALL_AL(alSourceStop(sources[index]));
									goto ESCAPE;
								}
								else if(environments[index].flag & GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_PAUSE)
								{
									GRAPH2D_CALL_AL(alSourcePause(sources[index]));
									goto ESCAPE;
								}
							}

							GRAPH2D_CALL_AL(alSourcef(sources[index], AL_GAIN, environments[index].fade[VOLUME].current));
ESCAPE:
							;
						}
					}
				}
			}
		}

		size_t Player::find()
		{
			for(size_t index = 0; index < size; index++)
			{
				if(environments[index].flag & GRAPH2D_SOUND_PLAYER_FLAG_ENABLE)
					continue;

				ALint state;
				GRAPH2D_CALL_AL(alGetSourcei(sources[index], AL_SOURCE_STATE, &state));
				if(state == AL_INITIAL || state == AL_STOPPED)
					return index;
			}
			return static_cast<size_t>(~0);
		}

		void Player::stopAll(const float second)
		{
			for(size_t index = 0; index < size; index++)
			{
				ALint state;
				GRAPH2D_CALL_AL(alGetSourcei(sources[index], AL_SOURCE_STATE, &state));
				if(state == AL_PLAYING)
					stop(index, second);
			}
		}

		void Player::pauseAll(const bool on, const float second)
		{
			for(size_t index = 0; index < size; index++)
			{
				ALint state;
				GRAPH2D_CALL_AL(alGetSourcei(sources[index], AL_SOURCE_STATE, &state));
				if(state == AL_PLAYING)
					pause(index, on, second);
			}
		}

		void Player::apply(const Parameter& parameter)
		{
			apply(find(), parameter);
		}

		void Player::apply(const size_t index, const Parameter& parameter)
		{
			if(index != ~0)
			{
				GRAPH2D_CALL_AL(alSourcefv(sources[index], AL_POSITION, parameter.position));
				GRAPH2D_CALL_AL(alSourcefv(sources[index], AL_VELOCITY, parameter.velocity));
				GRAPH2D_CALL_AL(alSourcefv(sources[index], AL_DIRECTION, parameter.direction));
			}
		}

		size_t Player::play(const size_t data)
		{
			const size_t index = find();
			if(index != ~0)
			{
				playDirect(index, data);
			}
			return index;
		}

		size_t Player::play(const size_t data, const Parameter& parameter)
		{
			const size_t index = find();
			if(index != ~0)
			{
				apply(index, parameter);
				playDirect(index, data);
			}
			return index;
		}

		void Player::playDirect(const size_t index, const size_t data)
		{
			// バッファをソースに設定する
			GRAPH2D_CALL_AL(alSourcei(sources[index], AL_BUFFER, dataManager->name(data)));
			// ループデータ？
			if(dataManager->getLoopPoint(data) == 0 && dataManager->getLoop(data))
			{
				GRAPH2D_CALL_AL(alSourcei(sources[index], AL_LOOPING, AL_TRUE));
			}
			// ソースを再生する
			GRAPH2D_CALL_AL(alSourcePlay(sources[index]));
			// プレイヤー情報を記録する
			environments[index].flag = GRAPH2D_SOUND_PLAYER_FLAG_ENABLE;
			environments[index].dataManagerIndex = data;
		}

		void Player::stop(const size_t index, const float second)
		{
			if(sources[index])
			{
				if(second <= 0)
				{
					GRAPH2D_CALL_AL(alSourceStop(sources[index]));
					environments[index].flag &= ~GRAPH2D_SOUND_PLAYER_FLAG_ENABLE;
				}
				else
				{
					setVolume(index, second, 0);
					environments[index].flag |= GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_STOP;
				}
			}
		}

		void Player::rewind(const size_t index)
		{
			GRAPH2D_CALL_AL(alSourceRewind(sources[index]));
		}

		void Player::pause(const size_t index, const bool on, const float second)
		{
			if(on)
			{
				// pause on
				environments[index].savedVolume = getVolume(index);
				if(second <= 0)
				{
					GRAPH2D_CALL_AL(alSourcePause(sources[index]));
				}
				else
				{
					setVolume(index, second, 0);
					environments[index].flag |= GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_PAUSE;
				}
			}
			else
			{
				// pause off
				GRAPH2D_CALL_AL(alSourcePlay(sources[index]));
				setVolume(index, second, environments[index].savedVolume);
			}
		}

		float Player::getVolume(const size_t index) const
		{
			return environments[index].fade[VOLUME].current;
		}

		void Player::setVolume(const size_t index, const float second, const float value)
		{
			if(second > 0)
			{
				environments[index].fade[VOLUME].target = value;
				environments[index].fade[VOLUME].timer = second;
				environments[index].fade[VOLUME].velocity = (value - environments[index].fade[VOLUME].current) * (1.f / second);
				environments[index].flag |= GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_FADE;
			}
			else
			{
				environments[index].fade[VOLUME].current = value;
				environments[index].flag &= ~GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_FADE;
				GRAPH2D_CALL_AL(alSourcef(sources[index], AL_GAIN, environments[index].fade[VOLUME].current));
			}
		}

		float Player::getPitch(const size_t index) const
		{
			return environments[index].fade[PITCH].current;
		}

		void Player::setPitch(const size_t index, const float second, const float value)
		{
			if(second > 0)
			{
				environments[index].fade[PITCH].target = value;
				environments[index].fade[PITCH].timer = second;
				environments[index].fade[PITCH].velocity = (value - environments[index].fade[PITCH].current) * (1.f / second);
				environments[index].flag |= GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_FADE;
			}
			else
			{
				environments[index].fade[PITCH].current = value;
				environments[index].flag &= ~GRAPH2D_SOUND_PLAYER_FLAG_VOLUME_FADE;
				GRAPH2D_CALL_AL(alSourcef(sources[index], AL_PITCH, environments[index].fade[PITCH].current));
			}
		}

		////////////////////////////////////////////////////////////
		Actor::Actor() : player(NULL)
		{
		}

		void Actor::initialize(Player* player)
		{
			this->player = player;
		}

		void Actor::finalize()
		{
			player = NULL;
		}

		void Actor::stopAll(const float second)
		{
			assert(player);
			player->stopAll(second);
		}

		void Actor::pauseAll(const bool on, const float second)
		{
			assert(player);
			player->pauseAll(on, second);
		}

		size_t Actor::play(const size_t data)
		{
			assert(player);
			return player->play(data);
		}

		void Actor::stop(const size_t index, const float second)
		{
			assert(player);
			player->stop(index, second);
		}

		void Actor::rewind(const size_t index)
		{
			assert(player);
			player->rewind(index);
		}

		void Actor::pause(const size_t index, const bool on, const float second)
		{
			assert(player);
			player->pause(index, on, second);
		}

		////////////////////////////////////////////////////////////
		MusicPlayer::MusicPlayer() : current(0)
		{
		}

		MusicPlayer::~MusicPlayer()
		{
		}

		void MusicPlayer::initialize(DataManager* dataManager)
		{
			player.initialize(2, dataManager);
		}

		void MusicPlayer::finalize()
		{
			player.finalize();
		}

		void MusicPlayer::stopAll(const float second)
		{
			player.stopAll(second);
		}

		void MusicPlayer::pauseAll(const bool on, const float second)
		{
			player.pauseAll(on, second);
		}

		size_t MusicPlayer::play(const size_t data)
		{
			current = static_cast<unsigned char>(player.play(data));
			return current;
		}

		size_t MusicPlayer::change(const size_t data, const float second)
		{
			player.stop(current, second);
			play(data);
			player.setVolume(current, 0, 0);
			player.setVolume(current, second, 1);
			return current;
		}

		void MusicPlayer::stop(const size_t index, const float second)
		{
			player.stop(index, second);
		}

		void MusicPlayer::pause(const size_t index, const bool on, const float second)
		{
			player.pause(index, on, second);
		}

		float MusicPlayer::getVolume(const size_t index) const
		{
			return player.getVolume(index);
		}

		void MusicPlayer::setVolume(const size_t index, const float second, const float value)
		{
			player.setVolume(index, second, value);
		}

		float MusicPlayer::getPitch(const size_t index) const
		{
			return player.getPitch(index);
		}

		void MusicPlayer::setPitch(const size_t index, const float second, const float value)
		{
			player.setPitch(index, second, value);
		}
	}
}
